package org.lansir.af.ui;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jivesoftware.smack.util.StringUtils;
import org.lansir.af.R;
import org.lansir.af.gtalk.GtalkApplication;
import org.lansir.af.gtalk.providers.AvatarProvider;
import org.lansir.af.gtalk.service.Contact;
import org.lansir.af.gtalk.service.PresenceAdapter;
import org.lansir.af.gtalk.service.aidl.IGtalkRosterListener;
import org.lansir.af.gtalk.service.aidl.IRoster;
import org.lansir.af.gtalk.service.aidl.IXmppFacade;
import org.lansir.af.gtalk.ui.GroupList;
import org.lansir.af.gtalk.ui.dialogs.builders.Alias;
import org.lansir.af.gtalk.ui.dialogs.builders.DeleteContact;
import org.lansir.af.gtalk.ui.dialogs.builders.ResendSubscription;
import org.lansir.af.gtalk.utils.GtalkBroadcastReceiver;
import org.lansir.af.gtalk.utils.SortedList;
import org.lansir.af.gtalk.utils.Status;
import org.lansir.af.ui.phone.ChatActivity;
import org.lansir.af.view.QuickActionWindow;

import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class ContactListFragment extends CommonFragment {
	private static final Intent SERVICE_INTENT = new Intent();
	static {
		SERVICE_INTENT.setComponent(new ComponentName("org.lansir.af", "org.lansir.af.gtalk.GtalkService"));
	}

	private static final String TAG = "ContactListFragment";

	private final List<String> mListGroup = new ArrayList<String>();

	/**
	 * Map containing a list of the different contacts of a given group. Each
	 * list is a @{link SortedList} so there is no need to sort it again.
	 * */
	private final Map<String, List<Contact>> mContactOnGroup = new HashMap<String, List<Contact>>();
	private final OnGtalkContactClickListener mOnContactClick = new OnGtalkContactClickListener();
	private final ServiceConnection mServConn = new GtalkServiceConnection();
	private final ComparatorContactListByStatusAndName<Contact> mComparator = new ComparatorContactListByStatusAndName<Contact>();
	private final GtalkRosterListener mGtalkRosterListener = new GtalkRosterListener();

	// private String mSelectedGroup;
	private IRoster mRoster;
	private Contact mSelectedContact;
	private IXmppFacade mXmppFacade;
	private SharedPreferences mSettings;

	private ExpandableListView elvGtalkContactList;
	private ContactListAdapter mContactListAdapter;
	private boolean mBinded;
	private View viewRoot;
	private QuickActionWindow mQuickActionWindow;

	@Override
	public void onCreate(Bundle saveBundle) {
		super.onCreate(saveBundle);
		mSettings = PreferenceManager.getDefaultSharedPreferences(getActivity());
		// a new QuickActionWindow object
		mQuickActionWindow = new QuickActionWindow(getActivity(), null, null);
		mQuickActionWindow.addItem(getResources().getDrawable(R.drawable.ic_quickaction_alias), getResources().getString(R.string.userinfo_label_alias), new OnClickListener() {
			public void onClick(View v) {
				Dialog alias = new Alias(getActivity(), mRoster, mSelectedContact).create();
				alias.show();
				mQuickActionWindow.dismiss();
			}
		});
		mQuickActionWindow.addItem(getResources().getDrawable(R.drawable.ic_quickaction_change_group), getResources().getString(R.string.userinfo_label_chg_group), new OnClickListener() {
			public void onClick(View v) {
				Intent in = new Intent(getActivity(), GroupList.class);
				in.putExtra("contact", mSelectedContact);
				startActivity(in);
				mQuickActionWindow.dismiss();
			}
		});
		mQuickActionWindow.addItem(getResources().getDrawable(R.drawable.ic_quickaction_resend), getResources().getString(R.string.userinfo_label_re_subscription), new OnClickListener() {
			public void onClick(View v) {
				Dialog subscription = new ResendSubscription(getActivity(), mXmppFacade, mSelectedContact).create();
				subscription.show();
				mQuickActionWindow.dismiss();
			}
		});
		mQuickActionWindow.addItem(getResources().getDrawable(R.drawable.ic_quickaction_delete), getResources().getString(R.string.userinfo_label_delete), new OnClickListener() {
			public void onClick(View v) {
				Dialog delete = new DeleteContact(getActivity(), mRoster, mSelectedContact).create();
				delete.show();
				mQuickActionWindow.dismiss();
			}
		});

		this.setHasOptionsMenu(true);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		viewRoot = inflater.inflate(R.layout.contactlist, null);

		elvGtalkContactList = (ExpandableListView) viewRoot.findViewById(R.id.etvGtalkContactList);
		mContactListAdapter = new ContactListAdapter(inflater);

		elvGtalkContactList.setAdapter(mContactListAdapter);
		
		elvGtalkContactList.setOnItemLongClickListener(new OnItemLongClickListener(){

			@Override
			public boolean onItemLongClick(AdapterView<?> parent, View v, int position, long id) {
				Log.v(TAG, "onItemLongClick");
				int[] xy = new int[2];
				// fills the array with the computed coordinates
				v.getLocationInWindow(xy);
				// rectangle holding the clicked view area
				Log.v(TAG, "x:" + xy[0] + ",y:" + xy[1] + ",width:" + v.getWidth() + ",height:" + v.getHeight());
				Rect rect = new Rect(xy[0], xy[1], xy[0] + v.getWidth(), xy[1] + v.getHeight());
				mQuickActionWindow.setLocation(v, rect);
				
				mSelectedContact = (Contact) parent.getItemAtPosition(position);
				mQuickActionWindow.show();
				return false;
			}
			
		});
		elvGtalkContactList.setOnChildClickListener(mOnContactClick);
		elvGtalkContactList.requestFocus();
//		registerForContextMenu(elvGtalkContactList);

		return viewRoot;
	}

	@Override
	public void onResume() {
		super.onResume();
		if (!mBinded)
			mBinded = getActivity().bindService(SERVICE_INTENT, mServConn, Context.BIND_AUTO_CREATE);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onPause() {
		super.onPause();
		try {
			if (mRoster != null) {
				mRoster.removeRosterListener(mGtalkRosterListener);
				mRoster = null;
			}
		} catch (RemoteException e) {
			Log.d("ContactList", "Remote exception", e);
		}
		if (mBinded) {
			getActivity().unbindService(mServConn);
			mBinded = false;
		}
		mXmppFacade = null;
	}	

	/**
	 * Listener on service event.
	 */
	private class GtalkRosterListener extends IGtalkRosterListener.Stub {
		/**
		 * Constructor.
		 */
		public GtalkRosterListener() {
		}

		/**
		 * {@inheritDoc} Simple stategy to handle the onEntriesAdded event. if
		 * contact has to be shown :
		 * <ul>
		 * <li>add him to his groups</li>
		 * <li>add him to the specials groups</>
		 * </ul>
		 */
		@Override
		public void onEntriesAdded(final List<String> addresses) throws RemoteException {
			final boolean hideDisconnected = mSettings.getBoolean(GtalkApplication.SHOW_OFFLINE_CONTACTS_KEY, false);
			for (String newName : addresses) {
				Contact contact = mRoster.getContact(newName);
				boolean visible = !hideDisconnected || Status.statusOnline(contact.getStatus());
				List<String> groups = contact.getGroups();
				if (visible) {
					for (String group : groups) {
						if (!mListGroup.contains(group)) {
							mListGroup.add(mListGroup.size() - 1, group);
							List<Contact> tmplist = new SortedList<Contact>(new LinkedList<Contact>(), mComparator);
							mContactOnGroup.put(group, tmplist);
						}
						List<Contact> contactByGroups = mContactOnGroup.get(group);
						contactByGroups.add(contact);

					}

					// add the contact to all and no groups
					addToSpecialList(contact);
				}
			}
			mContactListAdapter.notifyDataSetChanged();
		}

		/**
		 * {@inheritDoc} Simple stategy to handle the onEntriesDeleted event.
		 * <ul>
		 * <li>Remove the contact from all groups</li>
		 * </ul>
		 */
		@Override
		public void onEntriesDeleted(final List<String> addresses) throws RemoteException {
			Log.d(TAG, "onEntries deleted " + addresses);
			for (String cToDelete : addresses) {
				Contact contact = new Contact(cToDelete);
				for (Map.Entry<String, List<Contact>> entry : mContactOnGroup.entrySet()) {
					List<Contact> contactByGroups = entry.getValue();

					contactByGroups.remove(contact);

				}

			}
			mContactListAdapter.notifyDataSetChanged();

		}

		/**
		 * {@inheritDoc} Simple stategy to handle the onEntriesUpdated event.
		 * <ul>
		 * <li>Remove the contact from all groups</li>
		 * <li>if contact has to be shown add it to his groups</li>
		 * <li>if contact has to be shown add it to the specials groups</li>
		 * </ul>
		 */
		@Override
		public void onEntriesUpdated(final List<String> addresses) throws RemoteException {
			final boolean hideDisconnected = mSettings.getBoolean(GtalkApplication.SHOW_OFFLINE_CONTACTS_KEY, false);
			for (String adr : addresses) {
				Contact contact = mRoster.getContact(adr);
				boolean visible = !hideDisconnected || Status.statusOnline(contact.getStatus());
				List<String> groups = contact.getGroups();
				for (Map.Entry<String, List<Contact>> entry : mContactOnGroup.entrySet()) {
					List<Contact> contactByGroups = entry.getValue();

					contactByGroups.remove(contact);
					if (visible) {
						for (String group : groups) {
							if (!mListGroup.contains(group)) {
								mListGroup.add(mListGroup.size() - 1, group);
								List<Contact> tmplist = new SortedList<Contact>(new LinkedList<Contact>(), mComparator);
								mContactOnGroup.put(group, tmplist);
							}
							mContactOnGroup.get(group).remove(contact);
						}
					}

				}

				// add the contact to all and no groups
				if (visible) {
					addToSpecialList(contact);
				}
			}
			mContactListAdapter.notifyDataSetChanged();

		}

		/**
		 * {@inheritDoc} Simple stategy to handle the onPresenceChanged event.
		 * <ul>
		 * <li>Remove the contact from all groups</li>
		 * <li>if contact has to be shown add it to his groups</li>
		 * <li>if contact has to be shown add it to the specials groups</li>
		 * </ul>
		 */
		@Override
		public void onPresenceChanged(PresenceAdapter presence) throws RemoteException {
			String from = presence.getFrom();
			final boolean hideDisconnected = mSettings.getBoolean(GtalkApplication.SHOW_OFFLINE_CONTACTS_KEY, false);
			final Contact contact = mRoster.getContact(StringUtils.parseBareAddress(from));
			boolean visible = !hideDisconnected || Status.statusOnline(contact.getStatus());
			List<String> groups = contact.getGroups();
			for (Map.Entry<String, List<Contact>> entry : mContactOnGroup.entrySet()) {
				List<Contact> contactByGroups = entry.getValue();

				contactByGroups.remove(contact);
				if (visible) {
					if (groups.contains(entry.getKey())) {
						contactByGroups.add(contact);
					}
				}
			}
			if (visible) {
				addToSpecialList(contact);
			}
			mContactListAdapter.notifyDataSetChanged();
		}

		/**
		 * Add a contact to the special list No Group and All contacts. The
		 * contact will be added if the list is not the current list otherwise
		 * the list must be modified in a Handler.
		 * 
		 * @param contact
		 *            the contact to add.
		 */
		private void addToSpecialList(Contact contact) {
			List<String> groups = contact.getGroups();
			List<Contact> list = mContactOnGroup.get(getString(R.string.contact_list_all_contact));

			list.add(contact);

			list = mContactOnGroup.get(getString(R.string.contact_list_no_group));
			if (groups.isEmpty()) {
				list.add(contact);
			}
		}

	}

	/**
	 * The service connection used to connect to the Gtalk service.
	 */
	private class GtalkServiceConnection implements ServiceConnection {

		/**
		 * Constructor.
		 */
		public GtalkServiceConnection() {
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mXmppFacade = IXmppFacade.Stub.asInterface(service);
			try {
				mRoster = mXmppFacade.getRoster();
				if (mRoster != null) {
					List<String> tmpGroupList = mRoster.getGroupsNames();
					Collections.sort(tmpGroupList);
					mListGroup.clear();
					mListGroup.add(getString(R.string.contact_list_all_contact));
					mListGroup.addAll(tmpGroupList);
					mListGroup.add(getString(R.string.contact_list_no_group));
					assignContactToGroups(mRoster.getContactList(), tmpGroupList);
					makeSortedList(mContactOnGroup);

					mRoster.addRosterListener(mGtalkRosterListener);
					Log.d(TAG, "add roster listener");
					mContactListAdapter.notifyDataSetChanged();
				}
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			try {
				mRoster.removeRosterListener(mGtalkRosterListener);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
			mXmppFacade = null;
			mRoster = null;
			mListGroup.clear();
			mContactOnGroup.clear();
			mBinded = false;
		}

		/**
		 * Assign the differents contact to their groups. This methods will fill
		 * the mContactOnGroup map.
		 * 
		 * @param contacts
		 *            list of contacts
		 * @param groupNames
		 *            list of existing groups
		 */
		private void assignContactToGroups(List<Contact> contacts, List<String> groupNames) {
			boolean hideDisconnected = mSettings.getBoolean(GtalkApplication.SHOW_OFFLINE_CONTACTS_KEY, false);
			Log.v(TAG, "contact size:" + contacts.size());
			mContactOnGroup.clear();
			List<Contact> all = new LinkedList<Contact>();
			List<Contact> noGroups = new LinkedList<Contact>();
			for (String group : groupNames) {
				mContactOnGroup.put(group, new LinkedList<Contact>());
			}
			for (Contact c : contacts) {
				if (hideDisconnected && !Status.statusOnline(c.getStatus())) {
					continue;
				}
				all.add(c);
				List<String> groups = c.getGroups();
				if (groups.isEmpty())
					noGroups.add(c);
				else {
					for (String currentGroup : groups) {
						List<Contact> contactsByGroups = mContactOnGroup.get(currentGroup);
						contactsByGroups.add(c);
					}
				}
			}
			mContactOnGroup.put(getString(R.string.contact_list_no_group), noGroups);
			mContactOnGroup.put(getString(R.string.contact_list_all_contact), all);
		}

		/**
		 * Make the List of the map became Insertion sorted list.
		 * 
		 * @param map
		 *            the map to convert.
		 */
		private void makeSortedList(Map<String, List<Contact>> map) {
			for (Map.Entry<String, List<Contact>> entry : map.entrySet()) {
				List<Contact> l = entry.getValue();
				entry.setValue(new SortedList<Contact>(l, mComparator));
			}
		}
	}

	/**
	 * Comparator Contact by status and name.
	 */
	private static class ComparatorContactListByStatusAndName<T> implements Comparator<T> {
		/**
		 * Constructor.
		 */
		public ComparatorContactListByStatusAndName() {
		}

		@Override
		public int compare(T c1, T c2) {
			if (((Contact) c1).getStatus() < ((Contact) c2).getStatus()) {
				return 1;
			} else if (((Contact) c1).getStatus() > ((Contact) c2).getStatus()) {
				return -1;
			} else
				return ((Contact) c1).getName().compareToIgnoreCase(((Contact) c2).getName());
		}
	}

	/**
	 * Event simple click on item of the contact list.
	 */
	private class OnGtalkContactClickListener implements OnChildClickListener {

		@Override
		public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
			Log.v(TAG, "ContactListFragment:onClick");
			Contact c = mContactOnGroup.get(mListGroup.get(groupPosition)).get(childPosition);
			Intent i = new Intent(getActivity(), ChatActivity.class);
			i.setData(c.toUri());
			startActivity(i);

			return true;
		}
	}

	private class ContactListAdapter extends BaseExpandableListAdapter {
		private LayoutInflater mInflater;

		public ContactListAdapter(LayoutInflater inflater) {
			this.mInflater = inflater;
		}

		@Override
		public int getGroupCount() {

			return mListGroup.size();
		}

		@Override
		public int getChildrenCount(int groupPosition) {

			return mContactOnGroup.get(mListGroup.get(groupPosition)).size();
		}

		@Override
		public Object getGroup(int groupPosition) {

			return mListGroup.get(groupPosition);
		}

		@Override
		public Object getChild(int groupPosition, int childPosition) {

			return mContactOnGroup.get(mListGroup.get(groupPosition)).get(childPosition);
		}

		@Override
		public long getGroupId(int groupPosition) {

			return mListGroup.get(groupPosition).hashCode();
		}

		@Override
		public long getChildId(int groupPosition, int childPosition) {
			return mContactOnGroup.get(mListGroup.get(groupPosition)).get(childPosition).hashCode();
		}

		@Override
		public boolean hasStableIds() {
			return false;
		}

		@Override
		public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
			GroupViewHolder groupViewHolder;
			if (convertView == null) {
				convertView = mInflater.inflate(R.layout.gtalk_contact_list_group_item, null);
				groupViewHolder = new GroupViewHolder();
				groupViewHolder.tvContactlistGroup = (TextView) convertView.findViewById(R.id.tvContactlistGroup);
				convertView.setTag(groupViewHolder);
			} else {
				groupViewHolder = (GroupViewHolder) convertView.getTag();
			}
			groupViewHolder.tvContactlistGroup.setText(mListGroup.get(groupPosition));
			return convertView;
		}

		@Override
		public View getChildView(final int groupPosition,final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
			ChildViewHolder childViewHolder;
			if (convertView == null) {
				convertView = mInflater.inflate(R.layout.gtalk_contact_list_contact_item, null);
				childViewHolder = new ChildViewHolder();
				childViewHolder.ivAvatar = (ImageView) convertView.findViewById(R.id.ivAvatar);
				childViewHolder.tvContactlistPseudo = (TextView) convertView.findViewById(R.id.tvContactlistPseudo);
				childViewHolder.tvContactlistMsgPerso = (TextView) convertView.findViewById(R.id.tvContactlistMsgPerso);

				convertView.setTag(childViewHolder);
			} else {
				childViewHolder = (ChildViewHolder) convertView.getTag();
			}

			Contact c = mContactOnGroup.get(mListGroup.get(groupPosition)).get(childPosition);
			if (mRoster != null) {
				try {
					c = mRoster.getContact(c.getJID());
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
			
			bindView(childViewHolder, c);

			return convertView;
		}

		@Override
		public boolean isChildSelectable(int groupPosition, int childPosition) {
			return true;
		}

		private void bindView(ChildViewHolder childViewHolder, Contact curContact) {
			if (curContact != null) {
				childViewHolder.tvContactlistPseudo.setText(curContact.getName());
				childViewHolder.tvContactlistMsgPerso.setText(curContact.getMsgState());

				String avatarId = curContact.getAvatarId();
				int contactStatus = curContact.getStatus();
				Drawable avatar = getAvatarStatusDrawable(avatarId);
				childViewHolder.ivAvatar.setImageDrawable(avatar);
				childViewHolder.ivAvatar.setImageLevel(contactStatus);
			}
		}

		/**
		 * Get a LayerDrawable containing the avatar and the status icon. The
		 * status icon will change with the level of the drawable.
		 * 
		 * @param avatarId
		 *            the avatar id to retrieve or null to get default
		 * @return a LayerDrawable
		 */
		private Drawable getAvatarStatusDrawable(String avatarId) {
			Drawable avatarDrawable = null;
			if (avatarId != null) {
				Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(avatarId).build();
				InputStream in = null;
				try {
					try {
						in = getActivity().getContentResolver().openInputStream(uri);
						avatarDrawable = Drawable.createFromStream(in, avatarId);
					} finally {
						if (in != null)
							in.close();
					}
				} catch (IOException e) {
					Log.w(TAG, "Error while setting the avatar " + avatarId, e);
				}
			}
			if (avatarDrawable == null)
				avatarDrawable = getResources().getDrawable(R.drawable.gtalk_status_icon);
			LayerDrawable ld = (LayerDrawable) getResources().getDrawable(R.drawable.avatar_status);
			ld.setLayerInset(1, 36, 36, 0, 0);
			ld.setDrawableByLayerId(R.id.avatar, avatarDrawable);
			return ld;
		}

		private class GroupViewHolder {
			public TextView tvContactlistGroup;
		}

		private class ChildViewHolder {
			public ImageView ivAvatar;
			public TextView tvContactlistPseudo;
			public TextView tvContactlistMsgPerso;
		}

	}

}
